#include <Kharlia/Core/Common.h>
#include <Kharlia/Core/Logger.h>
#include <Kharlia/Core/ObjectUtil.h>
#include <Kharlia/Core/ObjectBase.h>
#include <Kharlia/Core/Subsystem.h>
#include <Kharlia/Core/Tasker.h>
#include <Kharlia/Core/App.h>
#include <Kharlia/Core/Converters.h>
#include <Kharlia/Core/IO.h>
#include <Kharlia/Core/Ptr.h>
#include <Kharlia/Core/WeakPtr.h>
//#include <Kharlia/Core/Modules/slsocketmodule.h>
#include <iostream>
#include <vector>

#include <Kharlia/Core/Debug.h>

#define RELATIVE_DLL_DIRECTORY_W L"bin"
#define USE_PYTHON_HEAP 0

namespace kharlia { namespace core {

namespace {

object GInnerModule;
object GModule;
shared_ptr<FLogger> GLogger;

} // namespace

int CallBaseTraverse(PyObject* self, visitproc visit, void* arg,
                     objects::instance<>* inst, traverseproc current) {
    PyTypeObject* base = Py_TYPE(self);
    traverseproc basefunc = base->tp_traverse;
    // Skip anything before current
    while (basefunc != current) {
        base = base->tp_base;
        assert(base);
        basefunc = base->tp_traverse;
    }
    // Skip current and any subtype_traverse that might be below us
    while (basefunc == current || basefunc == Py_TYPE(self)->tp_traverse) {
        base = base->tp_base;
        assert(base);
        basefunc = base->tp_traverse;
    }
    // Call the super traverse if we find one
    if (basefunc)
        return basefunc(self, visit, arg);
    // If not, visit the instance dict if one exists for Boost.Python.instance
    else if (base == objects::class_type().get() && inst->dict)
        Py_VISIT(inst->dict);
    return 0;
}

int CallBaseClear(PyObject* self, objects::instance<>* inst, inquiry current) {
    PyTypeObject* base = Py_TYPE(self);
    inquiry basefunc = base->tp_clear;
    while (basefunc != current) {
        base = base->tp_base;
        assert(base);
        basefunc = base->tp_clear;
    }
    while (basefunc == current || basefunc == Py_TYPE(self)->tp_clear) {
        base = base->tp_base;
        assert(base);
        basefunc = base->tp_clear;
    }
    if (basefunc)
        return basefunc(self);
    else if (base == objects::class_type().get() && inst->dict)
        Py_CLEAR(inst->dict);
    return 0;
}

#define AS_GC(o) ((PyGC_Head *)(o)-1)

// This function exists because several shared_ptr instances can
// share the same python reference thanks to Boost.Python
// Visiting each shared_ptr normally assumes each has its own reference,
// which causes counting errors with the GC.
//
// This function detects when a count is being done, and ensures that
// a particular shared_ptr deleter is only counted once during a cycle.
// To do this, a new field had to be added to Boost.Python's
// shared_ptr_deleter, the gc field.
int VisitSharedPtrDeleter(converter::shared_ptr_deleter* deleter,
                          visitproc visit, void* arg) {
    assert(deleter);
    PyObject* op = deleter->owner.get();
    if (op && PyObject_IS_GC(op)) {
        PyGC_Head *gc = AS_GC(op);
        bool isdecref = false;
        Py_ssize_t refs = gc->gc.gc_refs;

        // Since Python doesn't make its visit functions public,
        // we need to detect the right one here
        if (refs >= 0 && arg == nullptr) {
            isdecref = true;
            if (deleter->gc != 0)
                refs = ++gc->gc.gc_refs;
        }

        assert(!isdecref || refs > 0);
        Py_VISIT(op);

        if (isdecref) {
            // Make sure our previous detection was accurate
            assert(gc->gc.gc_refs == refs - 1);
            // Note how many counts we've done
            deleter->gc++;
        }
        else {
            // The garbage collector counts all refs at once, so in this
            // case, we're done and can clear the flag for the next
            // collection.
            deleter->gc = 0;
        }

    }
    return 0;
}

object GetInnerModuleAttr(str name) {
    assert(!GInnerModule.is_none());
    return GInnerModule.attr(name);
}

object GetModuleAttr(str name) {
    assert(!GModule.is_none());
    return GModule.attr(name);
}

void SetModuleAttr(str name, object value) {
    assert(!GModule.is_none());
    GModule.attr(name) = value;
}

shared_ptr<FLogger> const& GetMainLogger() {
    return GLogger;
}

void _SetMainLogger(shared_ptr<FLogger> const& logger) {
    GLogger = logger;
}


#if USE_PYTHON_HEAP

inline void* operator new(size_t size) {
    void* p = PyMem_Malloc(size);
    if (p == nullptr)
        throw std::bad_alloc();
    return p;
}

inline void* operator new(size_t size, std::nothrow_t const&) {
    return PyMem_Malloc(size);
}

inline void operator delete(void* p) {
    PyMem_Free(p);
}

inline void operator delete(void* p, std::nothrow_t const&) {
    PyMem_Free(p);
}

inline void* operator new[](size_t size) {
    void* p = PyMem_Malloc(size);
    if (p == nullptr)
        throw std::bad_alloc();
    return p;
}

inline void* operator new[](size_t size, std::nothrow_t const&) {
    return PyMem_Malloc(size);
}

inline void operator delete[](void* p) {
    PyMem_Free(p);
}

inline void operator delete[](void* p, std::nothrow_t const&) {
    PyMem_Free(p);
}

#endif // KH_USE_PYTHON_HEAP

void TestExtract(object obj) {
    //RegisterValueToSharedPtr<CObject>();
    //RegisterValueToSharedPtr<CApp>();
    //shared_ptr<CObject> ex2 = extract<shared_ptr<CObject>>(obj);
    //shared_ptr<CObject> ex = ExtractHeldSharedPtr<CObject>(obj);
    //shared_ptr<CApp> ex3 = extract<shared_ptr<CApp>>(obj);
    //bool m = IsManagingPyObject(ex);
    //bool m2 = IsManagingPyObject(ex2);
    //bool m3 = IsManagingPyObject(ex3);
    //std::cout << "DONE: " << m << std::endl;
}

static double tct = 0.0;

void TestCallTimeImpl(str test) {
    tct = StatGetClock();
}

void TestCallTime(str test) {
    object func = make_function(&TestCallTimeImpl);
    object args = py::make_tuple(test);
    PyObject* argsptr = args.ptr();
    PyObject* funcptr = func.ptr();
    double start = StatGetClock();
    PyObject* result = PyObject_Call(funcptr, argsptr, nullptr);
    Py_XDECREF(result);
    if (PyErr_Occurred())
        PyErr_Clear();
    double elapsed = tct - start;
    std::cout << "ELAPSED TEST CALL:" << elapsed * 1000 << std::endl;
}

object GetBoostPythonClassType() {
    return object(objects::class_metatype());
}
object GetBoostPythonInstanceType() {
    return object(objects::class_type());
}
str TestFileIO(str filename) {
    std::string fn = extract<char const*>(filename);
    intrusive_ptr<FFileLoadRequest> req(new FFileLoadRequest(fn, 0, -1));
    GetIOManagerPtr()->ExecuteSL(req);
    return str(req->GetResult());
}
void TestFileIODump(str filename, str dump) {
    intrusive_ptr<FFileDumpRequest> req(
        new FFileDumpRequest(extract<std::string>(filename),
                                extract<std::string>(dump)));
    GetIOManagerPtr()->ExecuteSL(req);
}
void CallbackFunc() {
    GetMainLogger()->Debug("NATIVE CALLBACK CALLED!");
}

void TestUnicode(unicode stro) {
    std::wstring cstro = extract<std::wstring>(stro);
    wchar_t const* cstroc = cstro.c_str();
    std::cout << cstroc << std::endl;
    unicode stro2 = stro;
    unicode stro3(cstroc);
    std::wstring cstro3 = extract<std::wstring>(stro3);
    wchar_t const* cstroc3 = cstro3.c_str();
    std::cout << cstroc3 << std::endl;

    std::size_t slen = len(stro);
    scoped_array<wchar_t> cstroc4(new wchar_t[slen + 1]); // +1 for null terminator
    std::size_t copied = stro.as_wchar(cstroc4.get(), slen + 1);
    assert(slen == copied);
    wchar_t end = cstroc4.get()[slen];
    wchar_t nullt = L'\0';
    std::cout << cstroc4.get() << std::endl;

    object strox(cstroc3);
    unicode stroxo = extract<unicode>(strox);

    str rstro = extract<str>(stro);
    char const* rstroc = PyString_AS_STRING(rstro.ptr());
    std::cout << rstroc << std::endl;

    unicode rstrou = extract<unicode>(rstro);
    std::wstring rstrouc = extract<std::wstring>(rstrou);
}

void TestWeakPtr(shared_ptr<CObject> op, shared_ptr<CApp> ap) {
    //weak_ptr<CObject> wop = MakeWeakPtr(op);
    //weak_ptr<CApp> wap = MakeWeakPtr(ap);
    //shared_ptr<void> spv((void*)0, converter::shared_ptr_deleter(handle<>()));
    //weak_ptr<FIORequest> wio = MakeWeakPtr(
    //    shared_ptr<FIORequest>(spv, (FIORequest*)0)
    //);
}

void TestPtr(object obj) {
    TPtr<CObject> p = extract<TPtr<CObject>>(obj);
    TPtr<CObject> p2 = p;

    TWeakPtr<CObject> w1(p);
    TWeakPtr<CObject> w2 = w1;

    TPtr<CObject> p3 = w2.Lock();
    TPtr<CApp> p4 = p3.Cast<CApp>();
    TPtr<CApp> p5 = p3.CastPtrDynamic<CApp>();
    TPtr<CApp> p6 = p3.CastPtrStatic<CApp>();
    int i = 5;
}

// Safely retrieve the instance dict of a Boost.Python.instance without creating one
object GetInstanceDict(object target) {
    if (PyObject_IsInstance(target.ptr(), upcast<PyObject>(objects::class_type().get()))) {
        auto inst = reinterpret_cast<objects::instance<>*>(target.ptr());
        return inst->dict ? object(handle<>(borrowed(inst->dict))) : object();
    }
    else {
        return target.attr("__dict__");
    }
}

void _ExitModule() {
    CIOManager::Exit();

    _FiniTaskerBindings();

    GConstructObjectFunc = object();
    GFindGlobalFunc = object();
    GLogger.reset();
    GInnerModule = object();
    GModule = object();
}

void _InitModule() {
    object __core = import("kharlia.__core");
    object atexit = import("atexit");

    // Register module exit function
    atexit.attr("register")(make_function(&_ExitModule));
    
    GInnerModule = __core;
    GModule = scope();

    // Logger initialized in CApp::RunImpl()

    _InitExceptions();
    _InitConverters();

    _InitObjectBindings();
    _InitAppBindings(); // calls _InitTaskerBindings()
    _InitIOBindings();

    //scope().attr("_slsocket") = InitSLSocketModule("kharlia._core._slsocket");

    def("GetBoostPythonClassType", &GetBoostPythonClassType);
    def("GetBoostPythonInstanceType", &GetBoostPythonInstanceType);
    def("GetInstanceDict", &GetInstanceDict);
    def("TestFileIO", &TestFileIO);
    def("TestFileIODump", &TestFileIODump);
    def("TestExtract", &TestExtract);
    def("TestUnicode", &TestUnicode);
    def("TestWeakPtr", &TestWeakPtr);
    def("TestCallTime", &TestCallTime);
    def("TestPtr", &TestPtr);
}

}} // namespace Kharlia

BOOST_PYTHON_MODULE(_core) {
    kharlia::core::_InitModule();
}